The fastest way to work with arrays
Β· 6 minutes of read Β· viewsAlthough my grandma liked to spend time feeding me sweets and telling lies, such as the claim that all methods in V8 are equal in performance, I knew that she was secretly hiding the truth of real JavaScript ninja techniques.π₯·π».
Even though the cake was delicious, it's important to know that a true Saiyan always sprinkles when he tinkles[1], and the real ninja always assumes that JavaScript's mysterious engines were written by triple agents who made their stuff so confusing just as a test of our intellectual superiority and endurance before we can truly enter the Ninja realm.
Seting up the task #
Imagine the situation where we want to have a workout array that contains all of our daily push-ups. And we do a lot of them. Like milions of them, and every push-up it's actually a pretty heavy-lifting for a memory.
The go-to approach by laydev would be something like this:
const workout = [];
for (let i; i < 10000000; i++) {
workout.push("push-up");
}
Looks okay, but it's actually quite slow. And don't even try to tell me that "everything will be fixed by a V8 engine that will magically turn this lazy-ass code into performance beast". We are not in the GCC town; it's the JavaScript ghetto! Things just don't get smoothed out here so easily.
Let's find a better, totally ninja-style, blazingly fast secret technique. But first, we need to reveal the first truth about JavaScript's arrays.
Empty array vs prepared array #
Should we start our workout with an empty mind or filled with memories of the times we failed as a developer? Or, to stay on track with the main quest, should we just create an empty workout array, or is it better to have it filled with empty slots ready to get sweatyπ¦?
const workout_empty = [];
const workout_slot_ready = ["slot",...,"slot"] // 10000000 slots
for (let i = 0; i < 10000000; i++) {
workout_empty[i] = "push-up";
}
for (let i = 0; i < 10000000; i++) {
workout_slot_ready[i] = "push-up";
}
The rule of thumb is that pushing that many elements to a completely empty array will be very slow. JavaScript generally prefers when the array is already populated[2], but let's actually test it.
The results exceed expectations. Pushing into the workout_slot_ready
array is more than 23x faster!
Slot_ready | Empty |
---|---|
π 93 ops/s | 4.1 ops/s |
Quickest way to create filled array #
So now we know that it's better to work with filled arrays, but how to create them quick? To find out let's set up the octagon for a fight between four opponents:
- old-style and cheerful Grandpa -
Array[i]
, - well-known and widely respected -
push()
method, - new kid on the block but with a cool hairstyle -
new Array().fill()
, - the guy who wears underpants on the outside -
Array.from()
.
Let's ask them to do 10 000 000 push-ups and see who will win in terms of performance.
// creating an empty array and filling it ol' style!
const workout = [];
for (let i = 0; i < 10000000; i++) {
workout[i] = "slot";
}
// creating an empty array and filling it up using a push
workout = [];
for (let i = 0; i < 10000000; i++) {
workout.push("slot");
}
// using Array's fill method to fill the array
workout = new Array(10000000).fill("slot", 0, 10000000);
// = new Array(10000000) performs exactly the same
// using Array's static from method with an object literal
workout = Array.from({ length: 10000000 }, () => "slot");
The results: new Array().fill
is 4.8x faster than push
and then almost 14x faster than Array.from()
creep!
new Array().fill | push() | Array[i] | Array.from() |
---|---|---|---|
π 18 ops/s | 3.7 ops/s | 3.5 ops/s | 1.3 ops/s |
Quickest way to insert elements into array #
So let's assume that our workout
array is already blazingly-fast-ninja-style-filledπ₯·π»β‘.
Now let's push some push-ups into the workout! By doing so, we need to approach a well-know JavaScript battleground of fight between forEach
and for
βοΈ.
// imperative approach
for (let i = 0; i < 10000000; i++) {
workout[i] = "push-up";
}
// declarative approach
workout.forEach((_, i) => workout[i] = "push-up");
This might cause some declarative devs to cry a little in the corner, but face the truth! Your fighting techniques are lame-o. Imperative 4 life, with performance 12.8x faster than declarative.
for | forEach |
---|---|
π 86 ops/s | 6.7 ops/s |
Final comparison #
Let's lay the groundwork for one final performance battle: the laydev technique from the beginning of our training and a ninja-style crafted method, versus a whole bunch of push-ups πͺπ»!
// π laydev style
const workout = [];
for (let i = 0; i < 10000000; i++) {
workout.push("push-up");
}
// π₯·π» ninja style
workout = new Array(10000000).fill("slot", 0, 10000000);
for (let i = 0; i < 10000000; i++) {
workout[i] = "push-up";
}
The results are just awesome - we are 4.2x faster![3] π
Ninja styla | Lay style |
---|---|
π 17 ops/s | 4 ops/s |
Tip
Next time you set yourself up for a good ninja push-up training, leave that fancy declarative kimono at home. Dress properly in ninja-imperative shinobi shΕzoku outfit and remember to warm-up your workout array with some fill
method[4] and use of good, ol' for
loop.
RIP in peace Akira Toriyama. β©οΈ
If you are keen to learn why is it like this check this blog. β©οΈ
It's worth mentioning that those results were measured using V8 browser (which is currently covering 75.5% of the market). SpiderMonkey actually makes ninja style slower than laydev (with 3.04% of the market). β©οΈ
Actually clean
new Array(10000000)
performs excatly as efficient as the one withfill
, so this step could be potentially skipped (although this way we might be creating holes in the array). β©οΈ